package com.clusterswisdom.horn.servlet;

import java.io.File;
import java.io.FileFilter;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import javax.servlet.ServletContext;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.clusterswisdom.horn.api.ioc.IChannelInitor;
import com.clusterswisdom.horn.api.tag.ChannelInitor;

public class ClassScaner {
	
	private Logger log = LoggerFactory.getLogger(ClassScaner.class);
	
	public void getClasses(ServletContext sc, String packageName) throws Exception {
        Set<URLClassLoader> loaders = new LinkedHashSet<URLClassLoader>(8);
        loaders.addAll(getClassLoaders(Thread.currentThread().getContextClassLoader()));
        loaders.addAll(getClassLoaders(ClassScaner.class.getClassLoader()));
 
        for (URLClassLoader cl : loaders) {
            for (URL url : cl.getURLs()) {
				if (url == null) {
					continue;
				}
				
				String file = url.getFile().toLowerCase();
				if (file.endsWith(".jar")) {
					processJarFile(sc, packageName, url);
						
				} else {
					lookupClassesInFileSystem(sc, null, new File(url.getFile()), true, cl);
					
				}
            }
        }
    }
 
    // 根据 baseClassLoader 找到所有的祖先 URLClassLoader (包括自己)
    private static Collection<URLClassLoader> getClassLoaders(ClassLoader baseClassLoader) {
        Collection<URLClassLoader> loaders = new ArrayList<URLClassLoader>(8);
        ClassLoader loader = baseClassLoader;
        while (loader != null) {
            if ("sun.misc.Launcher$ExtClassLoader".equals(loader.getClass().getName())) {
                break;
            }
            if (loader instanceof URLClassLoader) {
                loaders.add((URLClassLoader) loader);
            }
            loader = loader.getParent();
        }
        return loaders;
    }
    
    private void lookupClassesInFileSystem(ServletContext sc, String packageName, File packagePath, final boolean recursive, ClassLoader loader) {
    	if (!packagePath.exists() || !packagePath.isDirectory()) {
            return;
        }
        
        File[] dirfiles = packagePath.listFiles(new FileFilter() {
            public boolean accept(File file) {
                return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
            }
        });
 
        String packageNamePrefix = "";
        if (packageName != null && packageName.length() > 0) {
            packageNamePrefix = packageName + '.';
        }
        for (File file : dirfiles) {
            if (file.isDirectory()) {
                lookupClassesInFileSystem(sc, packageNamePrefix + file.getName(), file, recursive, loader);
                
            } else {
                // 去掉后面的 .class 只留下类名
                String klassName = packageNamePrefix + file.getName().substring(0, file.getName().length() - 6);
                processClass(sc, klassName);
                
            }
        }
    }
	
	public void scanPackage(ServletContext sce, String packageName) {
		scanByClassLoader(sce, packageName, Thread.currentThread().getContextClassLoader());
		//scanByClassLoader(sce, packageName, ClassScaner.this.getClass().getClassLoader());
	}
	
	private void scanByClassLoader(ServletContext sc, String packageName, ClassLoader loader) {
		try {
			String dirName = packageName.replaceAll("\\.", "/");
			Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(dirName);
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				if (url == null) {
					continue;
				}
				
				String protocol = url.getProtocol();
				if (protocol.equals("file")) {
					String packagePath = url.getPath();
					addClass(sc, packagePath, packageName);
						
				} else if (protocol.equals("jar")) {
					processJarFile(sc, packageName, url);
						
				}
			}
			
		} catch (Exception e) {
			e.printStackTrace();
			
		}
	}
	
	@SuppressWarnings("unchecked")
	private void processClass(ServletContext sc, String className) {
		try {
			log.debug("scan class " + className);
			Class<?> targetClass = Thread.currentThread().getContextClassLoader().loadClass(className);
//			if (targetClass.isAnnotationPresent(HornApi.class)) {
//				HornApi hornApiAnnotation = targetClass.getAnnotation(HornApi.class);
//				HornApiContainer.getContainer().addApi(hornApiAnnotation.channel(), hornApiAnnotation.service(), targetClass.newInstance());
//				
//			} else 
			
			if (targetClass.isAnnotationPresent(ChannelInitor.class)) {
				IChannelInitor initor = (IChannelInitor)targetClass.newInstance();
				initor.init(sc);
				
			}
			
		} catch (Error e) {
			e.printStackTrace();
			
		} catch (Exception e) {
			e.printStackTrace();
			
		}
	} 
	
	private void processJarFile(ServletContext sc, String packageName, URL url) {
		JarFile jarFile = null;
		try {
			if ("jar".equals(url.getProtocol())) {
				jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
				
	        } else {
	        	jarFile = new JarFile(url.getFile());
	        	
	        }
			
			Enumeration<JarEntry> jarEntries = jarFile.entries();
			while (jarEntries.hasMoreElements()) {
				JarEntry jarEntry = jarEntries.nextElement();
				String jarEntryName = jarEntry.getName();
				if (!jarEntryName.endsWith(".class")) {
					continue;
				}
				
				String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
				processClass(sc, className);
			}
			
		} catch (Exception e) {
			e.printStackTrace();
			
		}
	}

	private void addClass(ServletContext sc, String packagePath, String packageName) {
		File[] files = getClassFiles(packagePath);
		if (files == null) {
			return;
		}
		
		for (File file : files) {
			String fileName = file.getName();
			if (file.isFile()) {
				processClass(sc, getClassName(packageName, fileName));
				
			} else {
				String subPackagePath = getSubPackagePath(packagePath, fileName);
				String subPackageName = getSubPackageName(packageName, fileName);
				addClass(sc, subPackagePath, subPackageName);
				
			}
		}
	}

	private static String getSubPackagePath(String packagePath, String filePath) {
		String subPackagePath = filePath;
		if (packagePath != null && packagePath.trim().length() > 0) {
			subPackagePath = packagePath + "/" + subPackagePath;
		}
		return subPackagePath;
	}

	private static String getSubPackageName(String packageName, String filePath) {
		String subPackageName = filePath;
		if (packageName != null && packageName.trim().length() > 0) {
			subPackageName = packageName + "." + subPackageName;
		}
		return subPackageName;
	}

	private File[] getClassFiles(String packagePath) {
		return new File(packagePath).listFiles(new FileFilter() {
			@Override
			public boolean accept(File file) {
				return (file.isFile() && file.getName().endsWith(".class"))
						|| file.isDirectory();
			}
		});
	}

	private String getClassName(String packageName, String fileName) {
		String className = fileName.substring(0, fileName.lastIndexOf("."));
		if (packageName != null && packageName.trim().length() > 0) {
			className = packageName + "." + className;
		}
		return className;
	}

}